
#ifndef Velyn_ObjImporter_h
#define Velyn_ObjImporter_h

// Importing .obj: http://rastertek.com/dx11tut08.html

#include <iostream>
#include <fstream>
#include <cstdio>
#include <vector>

#include "OpenGL.h"
#include "Vector.h"

namespace Velyn {
        
    typedef Vector3 Vertex;
    typedef std::vector<Vertex> VertexList;
    
    typedef int Triangle;
    typedef std::vector<Triangle> TriangleList;

    typedef Vector2 Texture;
    typedef std::vector<Texture> TextureList;

    typedef Vector3 Normal;
    typedef std::vector<Normal> NormalList;

    typedef int Face;
    typedef std::vector<Face> FaceList;

    typedef int VertexIndex;
    typedef int TextureIndex;
    typedef int NormalIndex;

    class Mesh {
        
    private:
        
        VertexList vertexRecord;
        TriangleList triangleRecord;
        TextureList textureRecord;
        NormalList normalRecord;
        FaceList faceRecord;
        
        bool dirty;
        
    public:
        
        Mesh() : dirty(true) {}
        
        ~Mesh() {}
        
        bool isDirty() {
            if(dirty) {
                return true;
            } else {
                return false;
            }
        }
        
        void clean() {
            dirty = false;
        }
        
        int numberVertices() {
            return (int)vertexRecord.size();
        }
        
        int numberFaces() {
            return (int)faceRecord.size();
            // return(int) faceRecord.size() / SOMENUMBER
        }
        
        int numberTriangles() {
            return (int)(triangleRecord.size() / 3);
        }
        
        int size() {
            return (int)faceRecord.size();
        }
        
        void addVertex(const float x, const float y, const float z) {
            Vertex vertex;
            
            vertex.setValue(x, y, z);
            vertexRecord.push_back(vertex);
            dirty = true;
        }
        
        void addTriangle(const VertexIndex vertexIndex0, const VertexIndex vertexIndex1, const VertexIndex vertexIndex2) {
            triangleRecord.push_back(vertexIndex0);
            triangleRecord.push_back(vertexIndex1);
            triangleRecord.push_back(vertexIndex2);
        }
        
        void addTexture(const float u, const float v) {
            Texture texture;
            
            texture.setValue(u, v);
            textureRecord.push_back(texture);
            dirty = true;
        }
        
        void addNormal(const float x, const float y, const float z) {
            Normal normal;
            
            normal.setValue(x, y, z);
            normalRecord.push_back(normal);
            dirty = true;
        }
        
        void addFace(const VertexIndex vertexIndex, const TextureIndex textureIndex, const NormalIndex normalIndex) {
            faceRecord.push_back(vertexIndex);
            faceRecord.push_back(textureIndex);
            faceRecord.push_back(normalIndex);
            dirty = true;
        }
        
        void addFace(const VertexIndex vertexIndex, const NormalIndex normalIndex) {
            faceRecord.push_back(vertexIndex);
            faceRecord.push_back(normalIndex);
            dirty = true;
        }
        
        void addFace(const VertexIndex vertexIndex) {
            faceRecord.push_back(vertexIndex);
            dirty = true;
        }
        
        Vertex* getVertices() {
            return &(vertexRecord[0]);
        }
        
        Triangle* getTriangles() {
            return &(triangleRecord[0]);
        }
        
        Texture* getTextures() {
            return &(textureRecord[0]);
        }
        
        Normal* getNormals() {
            return &(normalRecord[0]);
        }
        
        Face* getFaces() {
            return &(faceRecord[0]);
        }
        
        void print() {
            std::cout << "Vertices:\n";
            for(int vertex = 0; vertex < vertexRecord.size(); vertex++) {
                std::cout << "v[" << vertex << "] ";
                vertexRecord[vertex].print();
            }
            
            std::cout << "Textures:\n";
            for(int texture = 0; texture < textureRecord.size(); texture++) {
                std::cout << "vt[" << texture << "] ";            
                textureRecord[texture].print();
            }
            
            std::cout << "Normals:\n";
            for(int normal = 0; normal < normalRecord.size(); normal++) {
                std::cout << "vn[" << normal << "] ";
                normalRecord[normal].print();
            }
            
            std::cout << "Faces:\n";
            for(int face = 0; face < faceRecord.size(); face += 2) {
                std::cout << "f[" << face << "] ";
                std::cout << faceRecord[face] << "//" << faceRecord[face + 1] << '\n';
            }
        }
    };

    class Loader {
        
    private:
        
        enum Type {
            VERTEX, TEXTURE, NORMAL, FACE, NOT_A_TYPE
        };
        
        Type checkBuffer(const char* buffer) {
            if(buffer[0] == 'v' and buffer[1] == ' ') {
                return VERTEX;
            } else if(buffer[0] == 'v' and buffer[1] == 't') {
                return TEXTURE;
            } else if(buffer[0] == 'v' and buffer[1] == 'n') {
                return NORMAL;
            } else if(buffer[0] == 'f' and buffer[1] == ' ') {
                return FACE;
            } 
            return NOT_A_TYPE;
        }
        
    public:
        
        Loader() {}
        
        ~Loader() {}
        
        Mesh* loadFile(const char* filename) {
            Mesh* mesh = new Mesh();
            FILE* file = std::fopen(filename, "r");
            
            char buffer[255];
            
            float x, y, z, u, v;
            
            VertexIndex vertexIndex[3];
            TextureIndex textureIndex[3];
            NormalIndex normalIndex[3];
            
            if(file) {
                while(std::fgets(buffer, 255, file) != NULL) {
                    Type type;
                    
                    type = checkBuffer(buffer);
                    
                    switch(type) {
                        case VERTEX: {
                            std::sscanf(buffer, "v %f %f %f", &x, &y, &z);
                            mesh->addVertex(x, y, z);
                        } break;
                            
                        case TEXTURE: {
                            std::sscanf(buffer, "vt %f %f", &u, &v);
                            mesh->addTexture(u, v);
                        } break;
                            
                        case NORMAL: {
                            std::sscanf(buffer, "vn %f %f %f", &x, &y, &z);
                            mesh->addNormal(x, y, z);
                        } break;
                            
                        case FACE: {
                            
                            //FIXME: this may not read in the '/' in correctly
                            std::sscanf(buffer, "f %d/%d/%d %d/%d/%d %d/%d/%d", 
                                        &vertexIndex[0], &textureIndex[0], &normalIndex[0],
                                        &vertexIndex[1], &textureIndex[1], &normalIndex[1],
                                        &vertexIndex[2], &textureIndex[2], &normalIndex[2]);
                            
                            mesh->addTriangle(vertexIndex[0] - 1, vertexIndex[1] - 1, vertexIndex[2] - 1);
                            
                            mesh->addFace(vertexIndex[0] - 1, normalIndex[0] - 1);
                            mesh->addFace(vertexIndex[1] - 1, normalIndex[1] - 1);
                            mesh->addFace(vertexIndex[2] - 1, normalIndex[2] - 1);
                        } break;
                            
                        case NOT_A_TYPE: {} break;
                            
                        default: {} break;
                    }
                }
            } else {
                std::printf("File does not exist!\n");
            }
            
            std::fclose(file);
            
            return mesh;
        }
        
        bool loadFile(const char* filename, Mesh* mesh) {
            bool result;
            // Mesh* mesh = new Mesh();
            FILE* file = std::fopen(filename, "r");
            
            char buffer[255];
            
            float x, y, z, u, v;
            
            VertexIndex vertexIndex[3];
            TextureIndex textureIndex[3];
            NormalIndex normalIndex[3];
            
            if(file) {
                while(std::fgets(buffer, 255, file) != NULL) {
                    Type type;
                    
                    type = checkBuffer(buffer);
                    
                    switch(type) {
                        case VERTEX: {
                            std::sscanf(buffer, "v %f %f %f", &x, &y, &z);
                            mesh->addVertex(x, y, z);
                        } break;
                            
                        case TEXTURE: {
                            std::sscanf(buffer, "vt %f %f", &u, &v);
                            mesh->addTexture(u, v);
                        } break;
                            
                        case NORMAL: {
                            std::sscanf(buffer, "vn %f %f %f", &x, &y, &z);
                            mesh->addNormal(x, y, z);
                        } break;
                            
                        case FACE: {
                            
                            //FIXME: this may not read in the '/' in correctly
                            std::sscanf(buffer, "f %d/%d/%d %d/%d/%d %d/%d/%d", 
                                        &vertexIndex[0], &textureIndex[0], &normalIndex[0],
                                        &vertexIndex[1],&textureIndex[1], &normalIndex[1],
                                        &vertexIndex[2], &textureIndex[2], &normalIndex[2]);
                            
                            mesh->addTriangle(vertexIndex[0] - 1, vertexIndex[1] - 1, vertexIndex[2] - 1);
                            
                            mesh->addFace(vertexIndex[0] - 1, normalIndex[0] - 1);
                            mesh->addFace(vertexIndex[1] - 1, normalIndex[1] - 1);
                            mesh->addFace(vertexIndex[2] - 1, normalIndex[2] - 1);
                        } break;
                            
                        case NOT_A_TYPE: {} break;
                            
                        default: {} break;
                    }
                }
                result = true;
            } else {
                result = false;
            }
            
            std::fclose(file);
            
            return result;
        }
    };

    class Renderer {
        
    private:
        
        Vertex* vertexRecord;
        Triangle* triangleRecord;
        Texture* textureRecord;
        Normal* normalRecord;
        Face* faceRecord;
        
        int numberFaces;
        int size;
        
        int numberTriangles;
        
        void update(Mesh* mesh) {
            numberFaces = mesh->numberFaces();
            size = mesh->size();
            numberTriangles = mesh->numberTriangles();
            
            vertexRecord = mesh->getVertices();
            triangleRecord = mesh->getTriangles();
            textureRecord = mesh->getTextures();
            normalRecord = mesh->getNormals();
            faceRecord = mesh->getFaces();
            
            mesh->clean();
        }
        
    public:
        
        Renderer() {}
        
        ~Renderer() {}
        
        void render(Mesh* mesh) {
            
            if(mesh->isDirty()) {
                update(mesh);
            }

            for(int face = 0; face < numberFaces; face += 6) {
                
                glBegin(GL_TRIANGLES);
                
                glNormal3f(normalRecord[faceRecord[face + 1]].x, 
                           normalRecord[faceRecord[face + 1]].y, 
                           normalRecord[faceRecord[face + 1]].z);
                
                glVertex3f(vertexRecord[faceRecord[face]].x, 
                           vertexRecord[faceRecord[face]].y, 
                           vertexRecord[faceRecord[face]].z);
                
                glNormal3f(normalRecord[faceRecord[face + 3]].x, 
                           normalRecord[faceRecord[face + 3]].y, 
                           normalRecord[faceRecord[face + 3]].z);
                
                glVertex3f(vertexRecord[faceRecord[face + 2]].x, 
                           vertexRecord[faceRecord[face + 2]].y, 
                           vertexRecord[faceRecord[face + 2]].z);
                
                glNormal3f(normalRecord[faceRecord[face + 5]].x, 
                           normalRecord[faceRecord[face + 5]].y, 
                           normalRecord[faceRecord[face + 5]].z);
                
                glVertex3f(vertexRecord[faceRecord[face + 4]].x, 
                           vertexRecord[faceRecord[face + 4]].y, 
                           vertexRecord[faceRecord[face + 4]].z);
                
                glEnd();
                
                /* glEnableClientState(GL_VERTEX_ARRAY);
                 glEnableClientState(GL_NORMAL_ARRAY);
                 
                 glVertexPointer(3, GL_FLOAT, 0, &(vertices[0]));
                 glNormalPointer(GL_FLOAT, (GLsizei)sizeof(normals), &(normals[0]));
                 
                 // FIXME: fix this.. http://www.songho.ca/opengl/gl_vertexarray.html
                 // glDrawElements(GL_TRIANGLES, (GLsizei)sizeof(triangles)*3, GL_UNSIGNED_BYTE, &(triangles[0]));
                 // glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices.size());
                 
                 // glNormalPointer(GL_FLOAT, (GLsizei)normals.size(), &(normals[0]));
                 glDrawElements(GL_TRIANGLES, (GLsizei)triangles.size(), GL_UNSIGNED_INT, &(triangles[0]));
                 
                 glDisableClientState(GL_VERTEX_ARRAY);
                 glDisableClientState(GL_NORMAL_ARRAY); */
            }
        }
    };

    class Model {

    private:
        
        const char* _name;
        Mesh* mesh;
        
        Loader loader;
        Renderer renderer;
        
    public:
        
        Model() {}
        
        Model(const char* filename) {
            mesh = loader.loadFile(filename);
        }
        
        Model(const char* name, const char* filename) {
            _name = name;
            mesh = loader.loadFile(filename);
        }
        
        ~Model() {
            delete _name;
            delete mesh;
        }
        
        void loadModel(const char* filename) {
            mesh = loader.loadFile(filename);
        }
        
        void loadModel(const char* name, const char* filename) {
            _name = name;
            mesh = loader.loadFile(filename);
        }
        
        void render() {
            renderer.render(mesh);
        }
    };
}

#endif
